
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <math.h>

#define GET_STATUS  0
#define UPDATE_SELF 1
#define JOIN_GAME   2
#define QUIT_GAME   3

#define TURN_LEFT     0x01
#define TURN_RIGHT    0x02
#define MOVE_LEFT     0x04
#define MOVE_RIGHT    0x08
#define MOVE_FORWARD  0x10
#define MOVE_BACKWARD 0x20
#define SHOOT         0x40
#define SPEED_UP      0x80

#define MAX_PLAYER    10
#define MAX_V         160
#define MAX_B         2
#define BALL_V        300
#define SHOOT_RANGE   0.4F
#define SPEED_UP_RATE 1.5F
#define ZIP_RATE      12
#define ZOOM_RATE     6
#define BALL_SLOW     0.35F

#define INTERVAL_usec 10000
#define BALL_R        10
#define MIN_R         10
#define MAX_R         20

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

typedef struct player_info {
    int id;
    char name[64];
    int side;
    float radius;
    float x;
    float y;
    float angle;  //radian system
    float v_r;
    float v_t;
    float b;      //angle velocity
    int speedup;
} PLAYER;

typedef struct ball_info {
    int pid;
    float radius;
    float x;
    float y;
    float v_x;
    float v_y;
} BALL;

typedef struct ground_info {
    float x;
    float y;
} FIELD;

typedef struct game_info {
    FIELD field;
    BALL ball;
    int player_count;
    PLAYER player[MAX_PLAYER];
} GAME;

typedef struct player_instruction {
    int id;
    int move;
} ORDER;

typedef union req_data {
    char join_name[64];
    int quit_id;
    ORDER order;
} req_data;

typedef struct request {
    int flag;
    req_data data;
} req;

GAME game;

void print_info() {
    printf("----field----\n");
    printf("x = %f\n", game.field.x);
    printf("y = %f\n", game.field.y);
    printf("----ball----\n");
    printf("radius = %f\n", game.ball.radius);
    printf("x = %f\n", game.ball.x);
    printf("y = %f\n", game.ball.y);
    printf("----player----\n");
    printf("count = %d\n", game.player_count);
    for (int i = 0; i < game.player_count; ++i) {
        printf("--------id = %d\n", game.player[i].id);
        printf("name = %s\n", game.player[i].name);
        printf("side = %d\n", game.player[i].side);
        printf("radius = %f\n", game.player[i].radius);
        printf("x = %f\n", game.player[i].x);
        printf("y = %f\n", game.player[i].y);
        printf("angle = %f\n\n", game.player[i].angle);
    }


}

void sort_players() {
    int counter = 0;
    for (int i = 0; i < MAX_PLAYER; ++i) {
        if (game.player[i].id != -1) {
            ++counter;
            if (i != counter - 1)
                game.player[counter - 1] = game.player[i];
        }
    }
    game.player_count = counter;
}

int set_player_loc(int i) {
    srand(time(NULL));
    game.player[i].angle = M_PI;
    game.player[i].x = rand() % (int) game.field.x;
    game.player[i].y = rand() % (int) (game.field.y / 2);

    if (game.player[i].side == 1) {
        game.player[i].angle = 0;
        game.player[i].y += game.field.y / 2;
    }
}

void handle_bad_request(int fd) {
    char buf[64] = "bad request";
    write(fd, buf, sizeof(buf));
}

void handle_status(int fd) {
    write(fd, &game, sizeof(game));
}

void handle_update(ORDER order) {
    int move = order.move;
    for (int i = 0; i < game.player_count; ++i) {
        if (game.player[i].id != order.id) continue;
        PLAYER p = game.player[i];
        if((move & SPEED_UP) == SPEED_UP) {
            p.speedup = 1;
        } else {
            p.speedup = 0;
        }

        if ((move & SHOOT) == SHOOT && game.ball.pid == p.id) {     //k
            float angle = p.angle;
            srand(time(NULL));
            angle += (rand() % 1000 - 500) / 1000.0F * SHOOT_RANGE;
            game.ball.v_x = BALL_V * sinf(angle);
            game.ball.v_y = -BALL_V * cosf(angle);
            game.ball.pid = -1;
        }
        p.b = 0;
        if ((move & TURN_LEFT) == TURN_LEFT) p.b -= MAX_B;   //j
        if ((move & TURN_RIGHT) == TURN_RIGHT) p.b += MAX_B;   //l

        int result = 4;
        if ((move & MOVE_LEFT) == MOVE_LEFT) result--;  //a
        if ((move & MOVE_RIGHT) == MOVE_RIGHT) result++;  //d
        if ((move & MOVE_FORWARD) == MOVE_FORWARD) result += 3; //w
        if ((move & MOVE_BACKWARD) == MOVE_BACKWARD) result -= 3; //s

        switch (result) {
            case 0:
                p.v_r = -MAX_V * M_SQRT1_2;
                p.v_t = -MAX_V * M_SQRT1_2;
                break;
            case 1:
                p.v_r = -MAX_V;
                p.v_t = 0;
                break;
            case 2:
                p.v_r = -MAX_V * M_SQRT1_2;
                p.v_t = MAX_V * M_SQRT1_2;
                break;
            case 3:
                p.v_r = 0;
                p.v_t = -MAX_V;
                break;
            case 4:
                p.v_r = 0;
                p.v_t = 0;
                break;
            case 5:
                p.v_r = 0;
                p.v_t = MAX_V;
                break;
            case 6:
                p.v_r = MAX_V * M_SQRT1_2;
                p.v_t = -MAX_V * M_SQRT1_2;
                break;
            case 7:
                p.v_r = MAX_V;
                p.v_t = 0;
                break;
            case 8:
                p.v_r = MAX_V * M_SQRT1_2;
                p.v_t = MAX_V * M_SQRT1_2;
                break;
        }


        game.player[i] = p;
    }
}

void handle_join(int fd, char *name) {
    printf("player join: %s\n", name);
    PLAYER p;

    int side0counter = 0;
    int max_id = 0;
    for (int i = 0; i < game.player_count; ++i) {
        if (game.player[i].id == -1) continue;
        if (game.player[i].side == 0) side0counter++;
        if (game.player[i].id > max_id) max_id = game.player[i].id;
    }
    int index = game.player_count;
    p.id = max_id + 1;
    p.side = 0;
    if (side0counter > game.player_count / 2) {
        p.side = 1;
    }
    strcpy(p.name, name);
    srand(time(NULL));
    p.radius = MAX_R;
    p.speedup = 0;
    game.player[index] = p;
    game.player_count++;
    set_player_loc(index);

    write(fd, &game.player[index], sizeof(p));
}

void handle_quit(int id) {
    for (int i = 0; i < game.player_count; ++i) {
        if (game.player[i].id != id) continue;
        game.player[i].id = -1;
        printf("player quit: %s\n", game.player[i].name);
    }
    sort_players();
}

void handle_request(int fd) {
    req request_buf;
    read(fd, &request_buf, sizeof(req));
    req_data data = request_buf.data;
    switch (request_buf.flag) {
        case GET_STATUS:
            handle_status(fd);
            break;
        case UPDATE_SELF:
            handle_update(data.order);
            break;
        case JOIN_GAME:
            handle_join(fd, data.join_name);
            break;
        case QUIT_GAME:
            handle_quit(data.quit_id);
            break;
        default:
            handle_bad_request(fd);
            break;
    }
    //print_info();
}

void *new_thread(void *pram) {
    pthread_detach(pthread_self());
    int fd = *((int *) pram);
    handle_request(fd);
    close(fd);
    free(pram);
    return NULL;
}

int steps = 0;
int time_passed = 0;


void game_step_in(int pram) {
    steps++;
    if (steps == 1000) {
        steps = 0;
        time_passed += 10;
        printf("[Heartbeat] Time passed: %d sec\n", time_passed);
    }

    //remove bad players
    sort_players();

    //players update
    for (int i = 0; i < game.player_count; ++i) {
        PLAYER p = game.player[i];
        float vx = p.v_r * sinf(p.angle) + p.v_t * cosf(p.angle);
        float vy = -p.v_r * cosf(p.angle) + p.v_t * sinf(p.angle);
        float step = INTERVAL_usec / 1000000.0F;
        if (p.speedup) {
            if (p.radius > MIN_R) {
                p.x += vx * step * SPEED_UP_RATE;
                p.y += vy * step * SPEED_UP_RATE;
                p.radius -= ZIP_RATE * step;
            } else {
                p.speedup = 0;
            }
        } else {
            p.x += vx * step;
            p.y += vy * step;
            if(p.radius < MAX_R)
                p.radius += ZOOM_RATE * step;
        }
        p.angle += p.b * step;
        game.player[i] = p;
    }

    //check if two players crash
    for (int i = 0; i < game.player_count; ++i) {
        for (int j = i + 1; j < game.player_count; ++j) {
            PLAYER p1 = game.player[i];
            PLAYER p2 = game.player[j];
            float dx = p2.x - p1.x;
            float dy = p2.y - p1.y;
            float d = sqrtf(dx * dx + dy * dy);
            if (d < (p1.radius + p2.radius)) {
                float dd = (p1.radius + p2.radius - d) / 2;
                p1.x -= dd / d * dx;
                p1.y -= dd / d * dy;
                p2.x += dd / d * dx;
                p2.y += dd / d * dy;
                game.player[i] = p1;
                game.player[j] = p2;
            }
        }
    }

    for (int i = 0; i < game.player_count; ++i) {
        PLAYER p = game.player[i];
        if (p.x < 0) p.x = 0;
        else if (p.x > game.field.x) p.x = game.field.x;
        if (p.y < 0) p.y = 0;
        else if (p.y > game.field.y) p.y = game.field.y;
        game.player[i] = p;
    }

    // start calculate ball
    BALL b = game.ball;

    // if ball is free, move it
    if (b.pid == -1) {
        float step = INTERVAL_usec / 1000000.0F;
        b.x += b.v_x * step;
        b.y += b.v_y * step;
        float rates = powf(BALL_SLOW, step);
        b.v_x *= rates;
        b.v_y *= rates;
        if (b.x < 0) {
            b.x = 0;
            b.v_x = -b.v_x;
        } else if (b.x > game.field.x) {
            b.x = game.field.x;
            b.v_x = -b.v_x;
        }
        if (b.y < 0) {
            b.y = 0;
            b.v_y = -b.v_y;
        } else if (b.y > game.field.y) {
            b.y = game.field.y;
            b.v_y = -b.v_y;
        }
    }

    // see if player gets a ball
    for (int i = 0; i < game.player_count; ++i) {
        PLAYER p = game.player[i];
        float dx = p.x - b.x;
        float dy = p.y - b.y;
        float d = sqrtf(dx * dx + dy * dy);
        if (d < p.radius + b.radius) {
            b.pid = p.id;
        }
    }

    // place the ball next to the holder
    if (b.pid != -1) {
        for (int i = 0; i < game.player_count; ++i) {
            if (game.ball.pid != game.player[i].id) continue;
            PLAYER p = game.player[i];
            b.x = p.x + (b.radius + p.radius) * sinf(p.angle);
            b.y = p.y - (b.radius + p.radius) * cosf(p.angle);
        }
    }

    //update ball info
    game.ball = b;
}

void reset_game() {
    sort_players();

    game.ball.x = game.field.x / 2;
    game.ball.y = game.field.y / 2;

    for (int i = 0; i < game.player_count; ++i) {
        set_player_loc(i);
    }
}

void init_game() {
    for (int i = 0; i < MAX_PLAYER; ++i) {
        game.player[i].id = -1;
    }
    game.field.x = 740;
    game.field.y = 1150;
    game.ball.radius = BALL_R;

    reset_game();

    signal(SIGALRM, game_step_in);
    struct itimerval buf;
    buf.it_value.tv_sec = 0;
    buf.it_value.tv_usec = INTERVAL_usec;
    buf.it_interval.tv_sec = 0;
    buf.it_interval.tv_usec = INTERVAL_usec;
    setitimer(ITIMER_REAL, &buf, NULL);
}


int main(int ac, char *av[]) {
    if (ac != 2) {
        printf("USE: CMD PORT\n");
        exit(EXIT_SUCCESS);
    }
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(av[1]));
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (const struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1)
        handle_error("cannot bind");
    listen(sockfd, 1);
    init_game();

    for (;;) {
        int newfd = accept(sockfd, NULL, NULL);
        int *pram = malloc(sizeof(int));
        *pram = newfd;
        pthread_t t;
        pthread_create(&t, NULL, new_thread, pram);
    }
    close(sockfd);
    return 0;
}

